All files / src/app/api/admin/support/tickets/[id]/messages route.ts

0% Statements 0/139
100% Branches 0/0
0% Functions 0/1
0% Lines 0/139

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140                                                                                                                                                                                                                                                                                       
export const dynamic = "force-dynamic";

/**
 * Admin Ticket Messages API
 * POST /api/admin/support/tickets/[id]/messages - Add agent message
 */

import { NextRequest, NextResponse } from 'next/server';
import { requireAdminRole, handleAuthError } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { CreateMessageSchema } from '@/lib/validation/support-schemas';
import { notifyNewMessage } from '@/lib/support/notification-utils';
import { TicketStatus, MessageSenderType, SupportTicketWithRelations } from '@/types/support';
import { handleError } from '@/lib/error-handler';
import { logger } from '@/lib/logging';

interface RouteParams {
  params: Promise<{ id: string }>;
}

export async function POST(request: NextRequest, { params }: RouteParams) {
  try {
    const session = await requireAdminRole();
    const { id } = await params;
    const agentId = Number(session.user.id);
    const agentName = session.user.name || 'Support Agent';

    // Verify ticket exists
    const ticket = await prisma.supportTicket.findUnique({
      where: { id },
      select: {
        id: true,
        ticketNumber: true,
        status: true,
        userId: true,
        firstResponseAt: true,
        assignedToId: true}});

    if (!ticket) {
      return NextResponse.json(
        { success: false, error: 'Ticket not found' },
        { status: 404 }
      );
    }

    // Validate input
    const body = await request.json();
    const validationResult = CreateMessageSchema.safeParse(body);
    if (!validationResult.success) {
      return NextResponse.json(
        {
          success: false,
          error: 'Validation failed',
          details: validationResult.error.flatten().fieldErrors},
        { status: 400 }
      );
    }

    const data = validationResult.data;

    // Create the message
    const message = await prisma.supportMessage.create({
      data: {
        ticketId: id,
        senderType: MessageSenderType.AGENT,
        senderId: agentId,
        senderName: agentName,
        content: data.content,
        isInternal: data.isInternal || false},
      include: {
        sender: {
          select: { id: true, name: true }},
        attachments: true}});

    // Build ticket update data
    const ticketUpdate: Record<string, unknown> = {};

    // Set first response time if not already set
    if (!ticket.firstResponseAt && !data.isInternal) {
      ticketUpdate.firstResponseAt = new Date();
    }

    // Auto-assign to responding agent if not assigned
    if (!ticket.assignedToId) {
      ticketUpdate.assignedToId = agentId;
    }

    // Update status if not an internal note
    if (!data.isInternal) {
      // If OPEN or AWAITING_AGENT, move to AWAITING_CUSTOMER
      if (
        ticket.status === TicketStatus.OPEN ||
        ticket.status === TicketStatus.AWAITING_AGENT
      ) {
        ticketUpdate.status = TicketStatus.AWAITING_CUSTOMER;
      } else if (ticket.status === TicketStatus.IN_PROGRESS) {
        // Keep IN_PROGRESS but we could also move to AWAITING_CUSTOMER
        ticketUpdate.status = TicketStatus.AWAITING_CUSTOMER;
      }
    }

    // Update ticket if there are changes
    if (Object.keys(ticketUpdate).length > 0) {
      await prisma.supportTicket.update({
        where: { id },
        data: ticketUpdate});
    }

    // Notify customer if not an internal note
    if (!data.isInternal && ticket.userId) {
      const ticketForNotify = {
        ...ticket,
        id,
        assignedToId: ticket.assignedToId || agentId} as unknown as SupportTicketWithRelations;

      await notifyNewMessage(ticketForNotify, agentName, data.isInternal);
    }

    logger.info('Agent message added to ticket', {
      category: 'ADMIN_SUPPORT',
      ticketId: id,
      messageId: message.id,
      agentId,
      isInternal: data.isInternal});

    return NextResponse.json(
      {
        success: true,
        data: message},
      { status: 201 }
    );
  } catch (error) {
    const authResponse = handleAuthError(error as Error);
    if (authResponse) return authResponse;

    logger.error('Error adding agent message', error instanceof Error ? error : new Error(String(error)), { category: 'ADMIN_SUPPORT' });
    return handleError(error);
  }
}